# -*- coding: utf-8 -*-
# @Author: Jerry
# @Date:   2022-02-24 16:56:28
# @Last Modified by:   Jerry
# @Last Modified time: 2022-03-10 12:40:41

# **********
'''
Canny边缘检测
目标: 将学习
  - Canny边缘检测的概念
  - OpenCV函数: cv.Canny()
理论:
  Canny Edge Detection是一种流行的边缘检测算法。它由John F. Canny发明
  1.这是一个多阶段算法，我们将经历每个阶段。
  2.降噪

由于边缘检测容易受到图像中噪声的影响，因此第一步是使用5x5高斯滤波器消除图像中的噪声。
1.查找图像的强度梯度
  然后使用Sobel核在水平和垂直方向上对平滑的图像进行滤波，以在水平方向(Gx)和垂直方向(Gy)上获得一阶导数。
  渐变方向始终垂直于边缘。将其舍入为代表垂直，水平和两个对角线方向的四个角度之一。
2.非极大值抑制 在获得梯度大小和方向后，将对图像进行全面扫描，以去除可能不构成边缘的所有不需要的像素。
  为此，在每个像素处，检查像素是否是其在梯度方向上附近的局部最大值。
3.磁滞阈值：该阶段确定哪些边缘全部是真正的边缘，哪些不是。
  为此，我们需要两个阈值minVal和maxVal。
  强度梯度大于maxVal的任何边缘必定是边缘，而小于minVal的那些边缘必定是非边缘，因此将其丢弃。
  介于这两个阈值之间的对象根据其连通性被分类为边缘或非边缘。
  如果将它们连接到“边缘”像素，则将它们视为边缘的一部分。否则，它们也将被丢弃。
'''


# **********
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
import os
rootpath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
datapath = os.path.join(rootpath,'data')
imgpath = lambda name: os.path.join(datapath,name)

# **********
# OpenCV中的Canny Edge检测
'''
OpenCV将以上所有内容放在单个函数 cv.Canny() 中。
cv.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges
  第一个参数是我们的输入图像。
  第二个和第三个参数分别是我们的minVal和maxVal
  第五个参数perture_size。它是用于查找图像渐变的Sobel内核的大小。默认情况下为3
  最后一个参数是L2gradient，它指定用于查找梯度幅度的方程式。
    如果为True，则使用上面提到的更精确的公式
    否则使用以下函数：Edge_Gradient(G)=|Gx|+|Gy|
    默认情况下，它为False

附加资源
Canny edge detector at Wikipedia：http://en.wikipedia.org/wiki/Canny_edge_detector
Canny Edge Detection Tutorial：http://dasl.unlv.edu/daslDrexel/alumni/bGreen/www.pages.drexel.edu/_weg22/can_tut.html by Bill Green, 2002.

https://blog.csdn.net/saltriver/article/details/80545571
Canny边缘检测是一种非常流行的边缘检测算法，是John Canny在1986年提出的。
它是一个多阶段的算法，即由多个步骤构成。
1.图像降噪
    梯度算子可以用于增强图像，本质上是通过增强边缘轮廓来实现的，也就是说是可以检测到边缘的。
    但是，它们受噪声的影响都很大。
    所以第一步就是想到要先去除噪声，因为噪声就是灰度变化很大的地方，所以容易被识别为伪边缘。
2.计算图像梯度
    计算图像梯度能够得到图像的边缘，因为梯度是灰度变化明显的地方，而边缘也是灰度变化明显的地方。
    但是这一步只能得到可能的边缘。因为灰度变化的地方可能是边缘，也可能不是边缘。
    这一步就有了所有可能是边缘的集合。
3.非极大值抑制
    通常灰度变化的地方都比较集中，将局部范围内的梯度方向上，灰度变化最大的保留下来，其它的不保留，这样可以剔除掉一大部分的点。
    将有多个像素宽的边缘变成一个单像素宽的边缘。即“胖边缘”变成“瘦边缘”。
4.阈值筛选
    通过非极大值抑制后，仍然有很多的可能边缘点，进一步的设置一个双阈值，即低阈值（low），高阈值（high）。
    灰度变化大于high的，设置为强边缘像素，低于low的，剔除。在low和high之间的设置为弱边缘。
    进一步判断，如果其领域内有强边缘像素，保留，如果没有，剔除。
    这样做是为了只保留强边缘轮廓的话，有些边缘可能不闭合，需要从满足low和high之间的点进行补充，使得边缘尽可能的闭合。
'''
def messi_canny():
    img = cv.imread(imgpath('messi5.jpg'),0)
    # img = cv.GaussianBlur(img,(7,7),0)
    edges = cv.Canny(img,100,200)
    plt.subplot(221),plt.imshow(img,cmap = 'gray')
    plt.title('Original Image'), plt.xticks([]), plt.yticks([])

    f=np.fft.fft2(img)
    fshift = np.fft.fftshift(f)
    magnitude_spectrum = 20*np.log(np.abs(fshift))
    plt.subplot(222),plt.imshow(magnitude_spectrum, cmap = 'gray')
    plt.title('Image Magnitude Spectrum'), plt.xticks([]), plt.yticks([])

    plt.subplot(223),plt.imshow(edges,cmap = 'gray')
    plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

    f=np.fft.fft2(edges)
    fshift = np.fft.fftshift(f)
    magnitude_spectrum = 20*np.log(np.abs(fshift))
    plt.subplot(224),plt.imshow(magnitude_spectrum, cmap = 'gray')
    plt.title('Edge Magnitude Spectrum'), plt.xticks([]), plt.yticks([])

    plt.show()


def lena_canny():
    # 读入图像
    img = cv.imread(imgpath('lena.jpg'))
    gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    # 图像降噪
    # 在OpenCV中，Canny函数本身应该没有将图像降噪包含在内。
    # 因此，实施Canny边缘检测时，需要在Canny函数外面执行图像降噪的过程。
    lena = cv.GaussianBlur(gray,(5,5),0)
    # Canny边缘检测，50为低阈值low，150为高阈值high
    # 调整low和high双阈值，能够得到不同的边缘效果。
    canny = cv.Canny(lena,50,150)
    cv.imshow('Canny',canny)
    cv.waitKey(0)
    cv.destroyAllWindows()
    # Canny在有噪声的情况下表现好不好，取决于前面的降噪过程，
    # 这也是为什么OpenCV将图像降噪放在Canny函数外面的原因，需要你自己精心调整它


if __name__ == '__main__':
    messi_canny()
    # lena_canny()









